/**
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @flow strict-local
 * @format
 * @oncall react_native
 */

const LOG_LEVELS = {
  trace: 0,
  info: 1,
  warn: 2,
  error: 3,
};

describe('console', () => {
  describe('.table(data, rows)', () => {
    let originalNativeLoggingHook;
    let logFn;

    beforeEach(() => {
      originalNativeLoggingHook = global.nativeLoggingHook;
      logFn = global.nativeLoggingHook = jest.fn();
    });

    afterEach(() => {
      global.nativeLoggingHook = originalNativeLoggingHook;
    });

    it('should print the passed array as a Markdown table', () => {
      console.table([
        {name: 'First', value: 500},
        {name: 'Second', value: 600},
        {name: 'Third', value: 700},
        {name: 'Fourth', value: 800, extraValue: true},
      ]);

      expect(logFn).toHaveBeenCalledTimes(1);
      expect(logFn.mock.lastCall).toEqual([
        `
| (index) | name     | value | extraValue |
| ------- | -------- | ----- | ---------- |
| 0       | 'First'  | 500   |            |
| 1       | 'Second' | 600   |            |
| 2       | 'Third'  | 700   |            |
| 3       | 'Fourth' | 800   | true       |`,
        LOG_LEVELS.info,
      ]);
    });

    it('should print the passed dictionary as a Markdown table', () => {
      console.table({
        first: {name: 'First', value: 500},
        second: {name: 'Second', value: 600},
        third: {name: 'Third', value: 700},
        fourth: {name: 'Fourth', value: 800, extraValue: true},
      });

      expect(logFn).toHaveBeenCalledTimes(1);
      expect(logFn.mock.lastCall).toEqual([
        `
| (index) | name     | value | extraValue |
| ------- | -------- | ----- | ---------- |
| first   | 'First'  | 500   |            |
| second  | 'Second' | 600   |            |
| third   | 'Third'  | 700   |            |
| fourth  | 'Fourth' | 800   | true       |`,
        LOG_LEVELS.info,
      ]);
    });

    it('should work with different types of values', () => {
      console.table([
        {
          string: '',
          number: 0,
          boolean: true,
          function: () => {},
          object: {a: 1, b: 2},
          null: null,
          undefined: undefined,
        },
        {
          string: 'a',
          number: 1,
          boolean: true,
          function: () => {},
          object: {a: 1, b: 2},
          null: null,
          undefined: undefined,
        },
        {
          string: 'aa',
          number: 2,
          boolean: false,
          function: () => {},
          object: {a: 1, b: 2},
          null: null,
          undefined: undefined,
        },
        {
          string: 'aaa',
          number: 3,
          boolean: false,
          function: () => {},
          object: {a: 1, b: 2},
          null: null,
          undefined: undefined,
        },
      ]);

      expect(logFn).toHaveBeenCalledTimes(1);
      expect(logFn.mock.lastCall).toEqual([
        `
| (index) | string | number | boolean | function | object | null | undefined |
| ------- | ------ | ------ | ------- | -------- | ------ | ---- | --------- |
| 0       | ''     | 0      | true    | ƒ        | {…}    | null | undefined |
| 1       | 'a'    | 1      | true    | ƒ        | {…}    | null | undefined |
| 2       | 'aa'   | 2      | false   | ƒ        | {…}    | null | undefined |
| 3       | 'aaa'  | 3      | false   | ƒ        | {…}    | null | undefined |`,
        LOG_LEVELS.info,
      ]);
    });

    it('should print the keys in all the objects', () => {
      console.table([
        {name: 'foo'},
        {name: 'bar', value: 1},
        {value: 2, surname: 'baz'},
        {address: 'other'},
      ]);

      expect(logFn).toHaveBeenCalledTimes(1);
      expect(logFn.mock.lastCall).toEqual([
        `
| (index) | name  | value | surname | address |
| ------- | ----- | ----- | ------- | ------- |
| 0       | 'foo' |       |         |         |
| 1       | 'bar' | 1     |         |         |
| 2       |       | 2     | 'baz'   |         |
| 3       |       |       |         | 'other' |`,
        LOG_LEVELS.info,
      ]);
    });

    it('should print an empty string for empty arrays', () => {
      console.table([]);

      expect(logFn).toHaveBeenCalledTimes(1);
      expect(logFn.mock.lastCall).toEqual([``, LOG_LEVELS.info]);
    });

    it('should print an empty string for empty dictionaries', () => {
      console.table({});

      expect(logFn).toHaveBeenCalledTimes(1);
      expect(logFn.mock.lastCall).toEqual([``, LOG_LEVELS.info]);
    });

    // This test is currently failing
    it('should print an indices table for an array of empty objects', () => {
      console.table([{}, {}, {}, {}]);

      expect(logFn).toHaveBeenCalledTimes(1);
      expect(logFn.mock.lastCall).toEqual([
        `
| (index) |
| ------- |
| 0       |
| 1       |
| 2       |
| 3       |`,
        LOG_LEVELS.info,
      ]);
    });

    it('should print an indices table for a dictionary of empty objects', () => {
      console.table({
        first: {},
        second: {},
        third: {},
        fourth: {},
      });

      expect(logFn).toHaveBeenCalledTimes(1);
      expect(logFn.mock.lastCall).toEqual([
        `
| (index) |
| ------- |
| first   |
| second  |
| third   |
| fourth  |`,
        LOG_LEVELS.info,
      ]);
    });

    it('should not modify the logged value', () => {
      global.nativeLoggingHook = jest.fn();

      const array = [
        {name: 'First', value: 500},
        {name: 'Second', value: 600},
        {name: 'Third', value: 700},
        {name: 'Fourth', value: 800, extraValue: true},
      ];
      const originalArrayValue = JSON.parse(JSON.stringify(array));

      console.table(array);

      expect(array).toEqual(originalArrayValue);

      const object = {
        first: {name: 'First', value: 500},
        second: {name: 'Second', value: 600},
        third: {name: 'Third', value: 700},
        fourth: {name: 'Fourth', value: 800, extraValue: true},
      };

      const originalObjectValue = JSON.parse(JSON.stringify(object));

      console.table(object);

      expect(object).toEqual(originalObjectValue);
    });

    it('should only print the selected columns, if specified (arrays)', () => {
      console.table(
        [
          {first: 1, second: 2, third: 3},
          {first: 4, second: 5},
          {third: 7, fourth: 8},
          {fifth: 9},
        ],
        // $FlowExpectedError[extra-arg]
        ['first', 'fifth'],
      );
      expect(logFn).toHaveBeenCalledTimes(1);
      expect(logFn.mock.lastCall).toEqual([
        `
| (index) | first | fifth |
| ------- | ----- | ----- |
| 0       | 1     |       |
| 1       | 4     |       |
| 2       |       |       |
| 3       |       | 9     |`,
        LOG_LEVELS.info,
      ]);
    });

    it('should only print the selected columns, if specified (dictionaries)', () => {
      console.table(
        {
          a: {first: 1, second: 2, third: 3},
          b: {first: 4, second: 5},
          c: {third: 7, fourth: 8},
          d: {fifth: 9},
        },
        // $FlowExpectedError[extra-arg]
        ['first', 'fifth'],
      );
      expect(logFn).toHaveBeenCalledTimes(1);
      expect(logFn.mock.lastCall).toEqual([
        `
| (index) | first | fifth |
| ------- | ----- | ----- |
| a       | 1     |       |
| b       | 4     |       |
| c       |       |       |
| d       |       | 9     |`,
        LOG_LEVELS.info,
      ]);
    });
  });
});
